Overview

This notebook evaluates the predicted rates of forgetting. The predictions are compared to the value derived at the end of each learning session to determine their accuracy.

Setup

library(fst)
library(data.table)
library(tidyr)
library(purrr)

Attaching package: 'purrr'
The following object is masked from 'package:data.table':

    transpose
library(furrr)
Loading required package: future
library(stringr)
library(ggplot2)
library(wesanderson)
library(lme4)
Loading required package: Matrix

Attaching package: 'Matrix'
The following objects are masked from 'package:tidyr':

    expand, pack, unpack
library(lmerTest)

Attaching package: 'lmerTest'
The following object is masked from 'package:lme4':

    lmer
The following object is masked from 'package:stats':

    step
library(multcomp)
Loading required package: mvtnorm
Loading required package: survival

Attaching package: 'survival'
The following object is masked from 'package:future':

    cluster
Loading required package: TH.data
Loading required package: MASS

Attaching package: 'TH.data'
The following object is masked from 'package:MASS':

    geyser
source(file.path("..", "scripts", "99_slimstampen_model_funs.R"))

Attaching package: 'dplyr'
The following object is masked from 'package:MASS':

    select
The following objects are masked from 'package:data.table':

    between, first, last
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
future::plan("multisession", workers = 6) # Set to desired number of cores
theme_set(theme_light(base_size = 14) +
            theme(strip.text = element_text(colour = "black")))

condition_colours <- wes_palette("Darjeeling1", n = 5)
condition_colours[c(2, 4, 5)] <- condition_colours[c(4, 5, 2)]

dataset_colours <- wes_palette("Darjeeling2", n = 5)[c(2, 3)]

Helper functions

load_predictions <- function(course) {
  
  pred_user <- read_fst(file.path("..", "data", "predictions", paste0("pred_v_obs_user_", str_replace_all(course, " ", "_"), ".fst")))
  pred_fact <- read_fst(file.path("..", "data", "predictions", paste0("pred_v_obs_fact_", str_replace_all(course, " ", "_"), ".fst")))
  pred_fact_user <- read_fst(file.path("..", "data", "predictions", paste0("pred_fact_and_user_", str_replace_all(course, " ", "_"), ".fst")))
  setDT(pred_user)
  setDT(pred_fact)
  setDT(pred_fact_user)
  
  pred_domain <- mean(unique(pred_fact, by = c("fact_id"))$pred_fact)
  pred_default <- 0.3
  
  # Combine
  pred_all <- merge(pred_user, pred_fact, by = c("user_id", "fact_id", "alpha", "n_reps"), all = TRUE)
  pred_all <- merge(pred_all, pred_fact_user, by = c("user_id", "fact_id", "alpha"), all = TRUE)
  pred_all[, pred_default := pred_default]
  pred_all[, pred_domain := pred_domain]
  
  pred_obs_long <- pivot_longer(pred_all, 
                                cols = pred_user:pred_domain,
                                names_to = "prediction_type",
                                names_prefix = "pred\\_")
  
  setDT(pred_obs_long)
  
  # Remove NA predictions and predictions without corresponding observations
  pred_obs_long <- pred_obs_long[!is.na(value)]
  pred_obs_long <- pred_obs_long[!is.na(alpha)]
  
  # Remove duplicates
  pred_obs_long <- unique(pred_obs_long)
  
  # Set proper labels
  condition_labels <- data.table(prediction_type = c("default", "domain", "fact", "user", "fact_user"),
                                 prediction_label = factor(c("Default", "Domain", "Fact", "Learner", "Fact & Learner"),
                                                           levels = c("Default", "Domain", "Fact", "Learner", "Fact & Learner")))
  pred_obs_long <- pred_obs_long[condition_labels, on = .(prediction_type)]
  
  return(pred_obs_long)
}

Rate of forgetting

Predicted rate of forgetting

pred_gl <- load_predictions("Grandes Lignes")
pred_ss <- load_predictions("Stepping Stones")

pred_both <- rbind(pred_gl[, course := "French"],
                   pred_ss[, course := "English"])

Distribution of predictions

p_rof_dist <- ggplot(pred_both, aes(x = value, fill = prediction_label)) +
  facet_grid(prediction_label ~ course, scales = "free_y") +
  geom_histogram(aes(y = ..density..), binwidth = .01) +
  guides(fill = "none") +
  labs(x = "Predicted rate-of-forgetting",
       y = "Density") +
  scale_fill_manual(values = condition_colours)

p_rof_dist
Warning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.
ℹ Please use `after_stat(density)` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.

ggsave(plot = p_rof_dist, file.path("..", "output", "rof_predictions_distribution.png"),
       device = "png", width = 5, height = 7.5)

Predicted vs. observed values

We compare the predicted rate of forgetting to the “observed” rate of forgetting, i.e., the rate of forgetting that was estimated at the end of the learning sequence.

To assess the accuracy of predictions, we compute the mean absolute error (MAE) as an aggregate statistic, as well as the absolute error (AE) of each individual prediction.

pred_mae <- pred_both[, .(mae = mean(abs(alpha - value)),
                          ae_se = sd(abs(alpha - value))/.N), 
                      by = .(course, prediction_label)]

  
n_obs <- pred_both[, .N, by = .(course, prediction_label)]

Plot predicted vs. observed values:

rof_min <- 0
rof_max <- 1
rof_breaks <- seq(0.1, 0.9, by = .2)

p_rof_pred_v_obs <- ggplot(pred_both,
                         aes(x = value, y = alpha, colour = prediction_label)) +
    facet_grid(course ~ prediction_label) +
    geom_hline(yintercept = 0.3, lty = 2) +
    geom_vline(xintercept = 0.3, lty = 2) +
    geom_abline(slope = 1, intercept = 0, lty = 3, alpha = 0.75) +
    geom_point(alpha = .1, size = .1, pch = ".") +
    geom_smooth(method = "lm", formula = y ~ x, colour = "black") +
  geom_label(data = pred_mae,
             aes(label = paste("MAE =", formatC(mae, digits = 3, flag = "#"))),
             x = rof_max, y = rof_min,
             hjust = 1, colour = "NA", size = 3,
             alpha = .9,
             label.size = NA) +
  geom_text(data = pred_mae,
            aes(label = paste("MAE =", formatC(mae, digits = 3, flag = "#"))),
            x = rof_max, y = rof_min,
            hjust = 1, colour = "black", size = 3) +
  geom_label(data = n_obs,
             aes(label = paste("n =", scales::comma(N))),
             x = rof_max,
             y = rof_max,
             hjust = 1, colour = "NA", size = 3,
             alpha = .9,
             label.size = NA) +
  geom_text(data = n_obs,
            aes(label = paste("n =", scales::comma(N))),
            x = rof_max,
            y = rof_max,
            hjust = 1, colour = "black", size = 3) +
  guides(colour = "none") +
  labs(x = "Predicted rate-of-forgetting α",
       y = "Observed rate-of-forgetting α") +
  coord_fixed(ratio = 1, xlim = c(rof_min, rof_max), ylim = c(rof_min, rof_max)) +
  scale_x_continuous(breaks = rof_breaks) +
  scale_y_continuous(breaks = rof_breaks) +
  scale_colour_manual(values = condition_colours)

p_rof_pred_v_obs

ggsave(plot = p_rof_pred_v_obs, file.path("..", "output", "rof_predicted_vs_observed.png"),
  device = "png", width = 10, height = 4.5)

rm(p_rof_pred_v_obs)

Prediction error

Calculate prediction error:

pred_both[, prediction_error := value - alpha]

Distribution of prediction error:

p_rof_pred_error <- ggplot(pred_both, aes(x = prediction_error, fill = prediction_label)) +
  facet_grid(prediction_label ~ course, scales = "free_y") +
  geom_histogram(aes(y = ..density..), binwidth = .01) +
  guides(fill = "none") +
  labs(x = "Rate-of-forgetting prediction error (predicted - observed)",
       y = "Density") +
  scale_fill_manual(values = condition_colours)

p_rof_pred_error

ggsave(plot = p_rof_pred_error, file.path("..", "output", "rof_prediction_error.png"),
       device = "png", width = 5, height = 7.5)

Absolute prediction error

To compare the magnitude of prediction errors between prediction methods, we look at absolute prediction error.

pred_both[, abs_prediction_error := abs(prediction_error)]

Distribution of absolute prediction error:

p_rof_abs_pred_error <- ggplot(pred_both, aes(x = abs_prediction_error, fill = prediction_label)) +
  facet_grid(prediction_label ~ course, scales = "free_y") +
  geom_histogram(aes(y = ..density..), binwidth = .01) +
  guides(fill = "none") +
  labs(x = "Absolute rate-of-forgetting prediction error",
       y = "Density") +
  scale_fill_manual(values = condition_colours)

p_rof_abs_pred_error

ggsave(plot = p_rof_abs_pred_error, file.path("..", "output", "rof_absolute_prediction_error.png"),
       device = "png", width = 5, height = 7.5)
pred_error_summarised <- pred_both[, .(error_mean = mean(abs_prediction_error), error_se = sd(abs_prediction_error)/.N), by = .(course, prediction_label)]

ggplot(pred_error_summarised, aes(x = prediction_label, y = error_mean, colour = course)) +
  geom_boxplot(data = pred_both,
               aes(y = abs_prediction_error, group = interaction(course, prediction_label)),
               colour = "grey70",
               width = .25,
               outlier.shape = NA,
               position = position_dodge(width = .5)) +
  geom_errorbar(aes(ymin = error_mean - error_se, ymax = error_mean + error_se), width = 0, position = position_dodge(width = .5)) +
  geom_point(position = position_dodge(width = .5)) +
  coord_cartesian(ylim = c(0, 0.175)) +
  labs(x = "Method",
       y = "Absolute rate-of-forgetting prediction error",
       colour = "Course")

Fit a regression model on absolute prediction error. The whole data set is too big to fit in a reasonable time, so we fit the model to a random subset of 1M predictions (which already takes ~24hrs).

Grandes Lignes
m_rof_pred_error_gl_file <- file.path("..", "data", "model_fits", "m_pred_error_Grandes_Lignes_1e6.rda")

if (file.exists(m_rof_pred_error_gl_file)) {
  load(m_rof_pred_error_gl_file)
} else {
  
  pred_gl_reg <- (
    pred_both
    [course == "French"]
    [sample(.N, 1e6), .(prediction_label, abs_prediction_error, user_id, fact_id)]
  )
  
  m_rof_pred_error_gl <- lmer(abs_prediction_error ~ prediction_label + 
                                (1 | user_id) + (1 | fact_id),
                              data = pred_gl_reg)
  
  save(m_rof_pred_error_gl, file = m_rof_pred_error_gl_file)
}

summary(m_rof_pred_error_gl)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: abs_prediction_error ~ prediction_label + (1 | user_id) + (1 |  
    fact_id)
   Data: pred_gl_reg

REML criterion at convergence: -3273096

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-3.6381 -0.5902 -0.1803  0.3945 14.5510 

Random effects:
 Groups   Name        Variance  Std.Dev.
 user_id  (Intercept) 0.0001461 0.01209 
 fact_id  (Intercept) 0.0001916 0.01384 
 Residual             0.0020919 0.04574 
Number of obs: 1000000, groups:  user_id, 40965; fact_id, 22884

Fixed effects:
                                 Estimate Std. Error         df t value
(Intercept)                     6.252e-02  1.701e-04  5.391e+04 367.666
prediction_labelDomain         -1.430e-03  1.446e-04  9.734e+05  -9.892
prediction_labelFact           -1.256e-02  1.454e-04  9.736e+05 -86.340
prediction_labelLearner        -4.216e-03  1.465e-04  9.744e+05 -28.786
prediction_labelFact & Learner -1.219e-02  1.474e-04  9.746e+05 -82.745
                               Pr(>|t|)    
(Intercept)                      <2e-16 ***
prediction_labelDomain           <2e-16 ***
prediction_labelFact             <2e-16 ***
prediction_labelLearner          <2e-16 ***
prediction_labelFact & Learner   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) prdc_D prdc_F prdc_L
prdctn_lblD -0.425                     
prdctn_lblF -0.416  0.496              
prdctn_lblL -0.413  0.493  0.490       
prdctn_lF&L -0.404  0.490  0.488  0.486

Compare different prediction types to each other:

ht_gl <- glht(m_rof_pred_error_gl, linfct = mcp(prediction_label = "Tukey"))
summary(ht_gl)

     Simultaneous Tests for General Linear Hypotheses

Multiple Comparisons of Means: Tukey Contrasts


Fit: lmer(formula = abs_prediction_error ~ prediction_label + (1 | 
    user_id) + (1 | fact_id), data = pred_gl_reg)

Linear Hypotheses:
                                Estimate Std. Error z value Pr(>|z|)    
Domain - Default == 0         -0.0014305  0.0001446  -9.892   <0.001 ***
Fact - Default == 0           -0.0125572  0.0001454 -86.340   <0.001 ***
Learner - Default == 0        -0.0042160  0.0001465 -28.786   <0.001 ***
Fact & Learner - Default == 0 -0.0121938  0.0001474 -82.745   <0.001 ***
Fact - Domain == 0            -0.0111266  0.0001456 -76.442   <0.001 ***
Learner - Domain == 0         -0.0027855  0.0001466 -18.999   <0.001 ***
Fact & Learner - Domain == 0  -0.0107633  0.0001475 -72.977   <0.001 ***
Learner - Fact == 0            0.0083412  0.0001474  56.590   <0.001 ***
Fact & Learner - Fact == 0     0.0003633  0.0001482   2.452    0.102    
Fact & Learner - Learner == 0 -0.0079779  0.0001490 -53.534   <0.001 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Adjusted p values reported -- single-step method)

Inspect the model’s residuals:

qqnorm(resid(m_rof_pred_error_gl))
qqline(resid(m_rof_pred_error_gl), col = "red")

plot(m_rof_pred_error_gl)

The QQ plot indicates quite a strong skew, which is not surprising, given that the distribution of absolute error is bounded by zero on the left but unbounded on the right. Assuming a Gamma distribution may be better, but models that use a Gamma distribution do not converge here. The LMER also gives a sufficiently accurate estimate of the means.

Stepping Stones
m_rof_pred_error_ss_file <- file.path("..", "data", "model_fits", "m_pred_error_Stepping_Stones_1e6.rda")

if (file.exists(m_rof_pred_error_ss_file)) {
  load(m_rof_pred_error_ss_file)
} else {
  
  pred_ss_reg <- (
    pred_both
    [course == "English"]
    [sample(.N, 1e6), .(prediction_label, abs_prediction_error, user_id, fact_id)]
  )
  
  m_pred_error <- lmer(abs_prediction_error ~ prediction_label + 
                         (1 | user_id) + (1 | fact_id),
                       data = pred_ss_reg)
  
  save(m_pred_error, file = m_rof_pred_error_ss_file)
}

summary(m_pred_error)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: abs_prediction_error ~ prediction_label + (1 | user_id) + (1 |  
    fact_id)
   Data: pred_ss_reg

REML criterion at convergence: -3582447

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-4.1157 -0.5163 -0.1535  0.2167 16.6397 

Random effects:
 Groups   Name        Variance  Std.Dev.
 user_id  (Intercept) 0.0001343 0.01159 
 fact_id  (Intercept) 0.0001022 0.01011 
 Residual             0.0014998 0.03873 
Number of obs: 1000000, groups:  user_id, 86084; fact_id, 45580

Fixed effects:
                                 Estimate Std. Error         df t value
(Intercept)                     5.297e-02  1.188e-04  1.484e+05  445.85
prediction_labelDomain         -2.163e-03  1.244e-04  9.714e+05  -17.39
prediction_labelFact           -7.743e-03  1.249e-04  9.710e+05  -62.02
prediction_labelLearner        -5.282e-03  1.255e-04  9.705e+05  -42.08
prediction_labelFact & Learner -7.926e-03  1.260e-04  9.701e+05  -62.91
                               Pr(>|t|)    
(Intercept)                      <2e-16 ***
prediction_labelDomain           <2e-16 ***
prediction_labelFact             <2e-16 ***
prediction_labelLearner          <2e-16 ***
prediction_labelFact & Learner   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) prdc_D prdc_F prdc_L
prdctn_lblD -0.522                     
prdctn_lblF -0.517  0.497              
prdctn_lblL -0.513  0.495  0.493       
prdctn_lF&L -0.509  0.493  0.492  0.489

Compare different prediction types to each other:

ht_ss <- glht(m_pred_error, linfct = mcp(prediction_label = "Tukey"))
summary(ht_ss)

     Simultaneous Tests for General Linear Hypotheses

Multiple Comparisons of Means: Tukey Contrasts


Fit: lmer(formula = abs_prediction_error ~ prediction_label + (1 | 
    user_id) + (1 | fact_id), data = pred_ss_reg)

Linear Hypotheses:
                                Estimate Std. Error z value Pr(>|z|)    
Domain - Default == 0         -0.0021631  0.0001244 -17.393   <1e-04 ***
Fact - Default == 0           -0.0077430  0.0001249 -62.018   <1e-04 ***
Learner - Default == 0        -0.0052823  0.0001255 -42.083   <1e-04 ***
Fact & Learner - Default == 0 -0.0079259  0.0001260 -62.907   <1e-04 ***
Fact - Domain == 0            -0.0055799  0.0001249 -44.665   <1e-04 ***
Learner - Domain == 0         -0.0031192  0.0001256 -24.834   <1e-04 ***
Fact & Learner - Domain == 0  -0.0057629  0.0001261 -45.717   <1e-04 ***
Learner - Fact == 0            0.0024607  0.0001261  19.518   <1e-04 ***
Fact & Learner - Fact == 0    -0.0001830  0.0001265  -1.447    0.597    
Fact & Learner - Learner == 0 -0.0026437  0.0001271 -20.798   <1e-04 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Adjusted p values reported -- single-step method)

Inspect the model’s residuals:

qqnorm(resid(m_pred_error))
qqline(resid(m_pred_error), col = "red")

Comparison
ht_gl_tidy <- broom::tidy(confint(ht_gl))
ht_ss_tidy <- broom::tidy(confint(ht_ss))
setDT(ht_gl_tidy)
setDT(ht_ss_tidy)

ht_both_tidy <- rbind(ht_gl_tidy[, course := "French"],
                      ht_ss_tidy[, course := "English"])
p_rof_pred_error_comp <- ggplot(ht_both_tidy, aes(x = contrast, y = estimate, ymin = conf.low, ymax = conf.high, colour = course)) +
  geom_hline(yintercept = 0, linetype = "11", colour = "grey60") +
  geom_errorbar(width = 0.1) + 
  geom_point() +
  labs(x = "Linear hypotheses",
       y = "Estimate",
       caption = "Tukey's range test. Error bars show 95% family-wise confidence level.",
       colour = "Course") +
    coord_flip()

p_rof_pred_error_comp

ggsave(plot = p_rof_pred_error_comp, file.path("..", "output", "rof_prediction_error_comparisons.png"),
       device = "png", width = 7.5, height = 5)
Summary plot
# Set significance level of comparisons manually, based on model output
pred_error_summarised$comparison <- c("***")
pred_error_summarised[c(3, 8), comparison := NA]
pred_error_summarised[c(5, 10), comparison := "n.s."]
# Add fitted values
pred_error_summarised[course == "French", error_fitted := predict(m_rof_pred_error_gl,
                                                newdata = pred_error_summarised[course == "French"],
                                                re.form = NA, 
                                                type = "response")]
pred_error_summarised[course == "English", error_fitted := predict(m_pred_error,
                                                newdata = pred_error_summarised[course == "English"],
                                                re.form = NA, 
                                                type = "response")]
p_rof_abs_pred_error_summ <- ggplot(pred_error_summarised, aes(x = reorder(prediction_label, -error_mean), y = error_mean, colour = course)) +
  geom_errorbar(aes(ymin = error_mean - error_se, ymax = error_mean + error_se), width = 0) +
  geom_line(aes(group = course), lty = 2) +
  geom_point() +
  geom_text(aes(label = comparison), 
            colour = "black",
            position = position_nudge(x = .5, y = c(rep(0, 9), .001)),
            hjust = .5) +
  labs(x = "Method",
       y = "Absolute rate-of-forgetting prediction error",
       colour = "Course") +
  scale_colour_manual(values = dataset_colours) +
  theme(legend.position = c(.85, .85))

p_rof_abs_pred_error_summ
Warning: Removed 2 rows containing missing values (`geom_text()`).

ggsave(plot = p_rof_abs_pred_error_summ, file.path("..", "output", "rof_absolute_prediction_error_summary.png"),
       device = "png", width = 7.5, height = 4.5)
Warning: Removed 2 rows containing missing values (`geom_text()`).
pred_error_summarised[, prediction_rank := frank(-error_mean), by = .(course)]
annotation_df_ss <- data.table(
  course = rep("English", 10),
  start = c(1, 1, 1, 1,
            2, 2, 2,
            3, 3,
            4
  ),
  end = c(2, 3, 4, 5,
          3, 4, 5,
          4, 5,
          5
  ),
  y = seq(max(pred_error_summarised$error_mean)*1.01 + .00675, max(pred_error_summarised$error_mean)*1.01, by = -.00075),
  label = c("p < .001", "p < .001", "p < .001", "p < .001",
            "p < .001", "p < .001", "p < .001",
            "p < .001", "p < .001",
            "n.s.")
)

annotation_df_gl <- data.table(
  course = rep("French", 10),
  start = c(1, 1, 1, 1,
            2, 2, 2,
            3, 3,
            4
  ),
  end = c(2, 3, 4, 5,
          3, 4, 5,
          4, 5,
          5
  ),
  y = seq(max(pred_error_summarised$error_mean)*1.01 + .00675, max(pred_error_summarised$error_mean)*1.01, by = -.00075),
  label = c("p < .001", "p < .001", "p < .001", "p < .001",
            "p < .001", "p < .001", "p < .001",
            "p < .001", "p < .001",
            "n.s.")
)

annotation_df_rof <- rbind(annotation_df_ss, annotation_df_gl)
annotation_df_rof[, label := factor(label, levels = c("p < .001", "p < .01", "p < .05", "n.s."))]
p_rof_pred_error_summary <- ggplot(pred_error_summarised, aes(x = prediction_rank, y = error_mean)) +
  facet_grid(~ course) +
  geom_line(data = annotation_df_rof,
            aes(x = 1, y = .05, lty = label, alpha = label, colour = NULL)) + # Dummy line to get legend
  geom_line(aes(colour = course, group = course)) +
  geom_errorbar(aes(ymin = error_mean - error_se, ymax = error_mean + error_se), width = 0) +
  geom_point(aes(colour = course, group = course)) +
  geom_label(aes(label = prediction_label), 
             colour = "black", 
             alpha = .9,
             label.size = NA, 
             nudge_y = -.0025) +
  labs(x = NULL,
       y = "Absolute prediction error:\nrate-of-forgetting α",
       colour = "Course") +
  scale_x_continuous(expand = expansion(add = .75), breaks = NULL) +
  scale_y_continuous(limits = c(0, NA)) +
  scale_colour_manual(values = dataset_colours) +
  scale_linetype_manual(values = c("p < .001" = 1,
                                   "p < .01" = 5,
                                   "p < .05" = 2,
                                   "n.s." = 3),
                        drop = FALSE,
                        name = "Pairwise comparison:") +
  scale_alpha_manual(values = c("p < .001" = 1,
                                "p < .01" = .75,
                                "p < .05" = .5, 
                                "n.s." = .25),
                     drop = FALSE,
                     name = "Pairwise comparison:") +
  guides(colour = "none") +
  ggsignif::geom_signif(data = annotation_df_rof,
                        aes(xmin = start, xmax = end, annotations = "", 
                            y_position = y, lty = label, alpha = label),
                        tip_length = 0,
                        manual = TRUE)  +
  theme(legend.position = "bottom",
        legend.justification = "right")
Warning in ggsignif::geom_signif(data = annotation_df_rof, aes(xmin = start, :
Ignoring unknown aesthetics: xmin, xmax, annotations, and y_position
p_rof_pred_error_summary
Warning: The following aesthetics were dropped during statistical transformation: xmin, xmax, y_position
ℹ This can happen when ggplot fails to infer the correct grouping structure in the data.
ℹ Did you forget to specify a `group` aesthetic or to convert a numerical variable into a factor?
Warning: The following aesthetics were dropped during statistical transformation: xmin, xmax, y_position
ℹ This can happen when ggplot fails to infer the correct grouping structure in the data.
ℹ Did you forget to specify a `group` aesthetic or to convert a numerical variable into a factor?

ggsave(file.path("..", "output", "rof_absolute_prediction_error_summary.png"),
       device = "png", width = 10, height = 8)
Warning: The following aesthetics were dropped during statistical transformation: xmin, xmax, y_position
ℹ This can happen when ggplot fails to infer the correct grouping structure in the data.
ℹ Did you forget to specify a `group` aesthetic or to convert a numerical variable into a factor?
The following aesthetics were dropped during statistical transformation: xmin, xmax, y_position
ℹ This can happen when ggplot fails to infer the correct grouping structure in the data.
ℹ Did you forget to specify a `group` aesthetic or to convert a numerical variable into a factor?

Improvement

How big was the improvement from worst to best prediction method?

French:

# Absolute change
ht_gl_tidy[contrast == "Fact - Default", estimate[1]]
[1] -0.01255716
# % change
scales::percent(
  ht_gl_tidy[contrast == "Fact - Default", estimate[1]] / fixef(m_rof_pred_error_gl)[[1]],
  accuracy = .1)
[1] "-20.1%"
-20.1%

English:

# Absolute change
ht_ss_tidy[contrast == "Fact & Learner - Default", estimate[1]]
[1] -0.007925937
# % change
scales::percent(
  ht_ss_tidy[contrast == "Fact & Learner - Default", estimate[1]] / fixef(m_pred_error)[[1]],
  accuracy = .1)
[1] "-15.0%"
-15.0%

Visualise prediction error

By learner

user_freq <- pred_both[, .N, by = .(course, prediction_label, user_id)]

pred_user_freq <- pred_both[user_freq[N > 50], on = .(course, prediction_label, user_id)]

pred_user_q <- pred_user_freq[, .(stat = c("whisker_low", "q25", "median", "q75", "whisker_high"),
                                  value = boxplot.stats(prediction_error, do.conf = FALSE, do.out = FALSE)$stats), by = .(course, prediction_label, user_id)]

pred_user_q <- pivot_wider(pred_user_q, names_from = "stat", values_from = "value")

pred_user_q <- pred_user_q %>%
  arrange(course, prediction_label, median) %>%
  group_by(course, prediction_label) %>%
  mutate(user_order = (1:n())/n())
ggplot(pred_user_q, aes(x = user_order)) +
  facet_grid(course ~ prediction_label) +
  geom_ribbon(aes(ymin = whisker_low, ymax = whisker_high, fill = course), alpha = .3) +
  geom_ribbon(aes(ymin = q25, ymax = q75, fill = course), alpha = .5) +
  geom_line(aes(y = median), lwd = 1) +
  geom_hline(data = NULL, yintercept = 0, lty = 3) +
    labs(x = "Learners",
       y = "Rate-of-forgetting prediction error\n(predicted - observed)") +
  scale_fill_manual(values = dataset_colours) +
  guides(fill = "none") +
  theme(axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    panel.grid.major.x = element_blank()) +
  NULL

ggsave(file.path("..", "output", "rof_prediction_error_by_learner.png"),
  device = "png", width = 10, height = 4.5)

Absolute error:

pred_user_q <- pred_user_freq[, .(stat = c("whisker_low", "q25", "median", "q75", "whisker_high"),
                                  value = boxplot.stats(abs_prediction_error, do.conf = FALSE, do.out = FALSE)$stats), by = .(course, prediction_label, user_id)]

pred_user_q <- pivot_wider(pred_user_q, names_from = "stat", values_from = "value")

pred_user_q <- pred_user_q %>%
  arrange(course, prediction_label, median) %>%
  group_by(course, prediction_label) %>%
  mutate(user_order = (1:n())/n())

ggplot(pred_user_q, aes(x = user_order)) +
  facet_grid(course ~ prediction_label) +
  geom_ribbon(aes(ymin = whisker_low, ymax = whisker_high, fill = course), alpha = .3) +
  geom_ribbon(aes(ymin = q25, ymax = q75, fill = course), alpha = .5) +
  geom_line(aes(y = median), lwd = 1) +
    labs(x = "Learners",
       y = "Absolute rate-of-forgetting prediction error") +
  scale_fill_manual(values = dataset_colours) +
  guides(fill = "none") +
  theme(axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    panel.grid.major.x = element_blank()) +
  NULL

ggsave(file.path("..", "output", "rof_abs_prediction_error_by_learner.png"),
  device = "png", width = 10, height = 4.5)
ggplot(pred_user_q, aes(x = user_order, group = prediction_label, colour = prediction_label)) +
  facet_grid(~ course) +
  geom_ribbon(aes(ymin = q25, ymax = q75, fill = prediction_label), colour = NA, alpha = .15) +
  geom_line(aes(y = median), lwd = 1) +
    labs(x = "Learners",
       y = "Absolute rate-of-forgetting prediction error",
       colour = "Prediction\nmethod",
       fill = "Prediction\nmethod") +
  scale_colour_manual(values = condition_colours) +
  scale_fill_manual(values = condition_colours) +
  theme(axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    panel.grid.major.x = element_blank()) +
  NULL

ggsave(file.path("..", "output", "rof_abs_prediction_error_by_learner_condensed.png"),
  device = "png", width = 10, height = 4.5)

By fact

fact_freq <- pred_both[, .N, by = .(course, prediction_label, fact_id)]

pred_fact_freq <- pred_both[fact_freq[N > 50], on = .(course, prediction_label, fact_id)]

pred_fact_q <- pred_fact_freq[, .(stat = c("whisker_low", "q25", "median", "q75", "whisker_high"),
                                  value = boxplot.stats(prediction_error, do.conf = FALSE, do.out = FALSE)$stats), by = .(course, prediction_label, fact_id)]

pred_fact_q <- pivot_wider(pred_fact_q, names_from = "stat", values_from = "value")

pred_fact_q <- pred_fact_q %>%
  arrange(course, prediction_label, median) %>%
  group_by(course, prediction_label) %>%
  mutate(fact_order = (1:n())/n())
ggplot(pred_fact_q, aes(x = fact_order)) +
  facet_grid(course ~ prediction_label) +
  geom_ribbon(aes(ymin = whisker_low, ymax = whisker_high, fill = course), alpha = .3) +
  geom_ribbon(aes(ymin = q25, ymax = q75, fill = course), alpha = .5) +
  geom_line(aes(y = median), lwd = 1) +
  geom_hline(data = NULL, yintercept = 0, lty = 3) +
    labs(x = "Facts",
       y = "Rate-of-forgetting prediction error\n(predicted - observed)") +
  scale_fill_manual(values = dataset_colours) +
  guides(fill = "none") +
  theme(axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    panel.grid.major.x = element_blank()) +
  NULL

ggsave(file.path("..", "output", "rof_prediction_error_by_fact.png"),
  device = "png", width = 10, height = 4.5)

Absolute error:

pred_fact_q <- pred_fact_freq[, .(stat = c("whisker_low", "q25", "median", "q75", "whisker_high"),
                                  value = boxplot.stats(abs_prediction_error, do.conf = FALSE, do.out = FALSE)$stats), by = .(course, prediction_label, fact_id)]

pred_fact_q <- pivot_wider(pred_fact_q, names_from = "stat", values_from = "value")

pred_fact_q <- pred_fact_q %>%
  arrange(course, prediction_label, median) %>%
  group_by(course, prediction_label) %>%
  mutate(fact_order = (1:n())/n())

ggplot(pred_fact_q, aes(x = fact_order)) +
  facet_grid(course ~ prediction_label) +
  geom_ribbon(aes(ymin = whisker_low, ymax = whisker_high, fill = course), alpha = .3) +
  geom_ribbon(aes(ymin = q25, ymax = q75, fill = course), alpha = .5) +
  geom_line(aes(y = median), lwd = 1) +
    labs(x = "Facts",
       y = "Absolute rate-of-forgetting prediction error") +
  scale_fill_manual(values = dataset_colours) +
  guides(fill = "none") +
  theme(axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    panel.grid.major.x = element_blank()) +
  NULL

ggsave(file.path("..", "output", "rof_abs_prediction_error_by_fact.png"),
  device = "png", width = 10, height = 4.5)
ggplot(pred_fact_q, aes(x = fact_order, group = prediction_label, colour = prediction_label)) +
  facet_grid(~ course) +
  geom_ribbon(aes(ymin = q25, ymax = q75, fill = prediction_label), colour = NA, alpha = .15) +
  geom_line(aes(y = median), lwd = 1) +
    labs(x = "Facts",
       y = "Absolute rate-of-forgetting prediction error",
       colour = "Prediction\nmethod",
       fill = "Prediction\nmethod") +
  scale_colour_manual(values = condition_colours) +
  scale_fill_manual(values = condition_colours) +
  theme(axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    panel.grid.major.x = element_blank()) +
  NULL

ggsave(file.path("..", "output", "rof_abs_prediction_error_by_fact_condensed.png"),
  device = "png", width = 10, height = 4.5)
ggplot(pred_both, aes(x = abs_prediction_error, colour = prediction_label, fill = prediction_label)) +
  facet_grid(~ course) +
  geom_density(alpha = .1) +
      labs(x = "Absolute rate-of-forgetting prediction error",
       y = "Density",
       colour = "Prediction\nmethod",
       fill = "Prediction\nmethod") +
  scale_colour_manual(values = condition_colours) +
  scale_fill_manual(values = condition_colours) +
  coord_cartesian(ylim = c(0, 100), xlim = c(0, .25)) +
  NULL

ggsave(file.path("..", "output", "rof_abs_prediction_error_density.png"),
  device = "png", width = 10, height = 4.5)

Session info

sessionInfo()
R version 4.3.1 (2023-06-16)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Sonoma 14.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.11.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/Amsterdam
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] fstcore_0.9.14    dplyr_1.1.3       multcomp_1.4-25   TH.data_1.1-2    
 [5] MASS_7.3-60       survival_3.5-7    mvtnorm_1.2-3     lmerTest_3.1-3   
 [9] lme4_1.1-34       Matrix_1.6-1.1    wesanderson_0.3.6 ggplot2_3.4.3    
[13] stringr_1.5.0     furrr_0.3.1       future_1.33.0     purrr_1.0.2      
[17] tidyr_1.3.0       data.table_1.14.8 fst_0.9.8        

loaded via a namespace (and not attached):
 [1] gtable_0.3.4        xfun_0.40           bslib_0.5.1        
 [4] lattice_0.21-9      numDeriv_2016.8-1.1 vctrs_0.6.3        
 [7] tools_4.3.1         generics_0.1.3      parallel_4.3.1     
[10] sandwich_3.0-2      tibble_3.2.1        fansi_1.0.4        
[13] pkgconfig_2.0.3     lifecycle_1.0.3     compiler_4.3.1     
[16] farver_2.1.1        munsell_0.5.0       codetools_0.2-19   
[19] htmltools_0.5.6     sass_0.4.7          yaml_2.3.7         
[22] crayon_1.5.2        pillar_1.9.0        nloptr_2.0.3       
[25] jquerylib_0.1.4     cachem_1.0.8        boot_1.3-28.1      
[28] nlme_3.1-163        parallelly_1.36.0   tidyselect_1.2.0   
[31] digest_0.6.33       stringi_1.7.12      listenv_0.9.0      
[34] labeling_0.4.3      splines_4.3.1       fastmap_1.1.1      
[37] grid_4.3.1          colorspace_2.1-0    cli_3.6.1          
[40] magrittr_2.0.3      utf8_1.2.3          broom_1.0.5        
[43] withr_2.5.1         backports_1.4.1     scales_1.2.1       
[46] rmarkdown_2.25      globals_0.16.2      ggsignif_0.6.4     
[49] zoo_1.8-12          evaluate_0.22       knitr_1.44         
[52] mgcv_1.9-0          rlang_1.1.1         Rcpp_1.0.11        
[55] glue_1.6.2          rstudioapi_0.15.0   minqa_1.2.6        
[58] jsonlite_1.8.7      R6_2.5.1           
LS0tCnRpdGxlOiAiRXZhbHVhdGUgcmF0ZSBvZiBmb3JnZXR0aW5nIHByZWRpY3Rpb25zIgphdXRob3I6ICJNYWFydGVuIHZhbiBkZXIgVmVsZGUiCmRhdGU6ICJMYXN0IHVwZGF0ZWQ6IGByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHNtYXJ0OiBubwogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgZ2l0aHViX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCgoKIyBPdmVydmlldwoKVGhpcyBub3RlYm9vayBldmFsdWF0ZXMgdGhlIHByZWRpY3RlZCByYXRlcyBvZiBmb3JnZXR0aW5nLgpUaGUgcHJlZGljdGlvbnMgYXJlIGNvbXBhcmVkIHRvIHRoZSB2YWx1ZSBkZXJpdmVkIGF0IHRoZSBlbmQgb2YgZWFjaCBsZWFybmluZyBzZXNzaW9uIHRvIGRldGVybWluZSB0aGVpciBhY2N1cmFjeS4KCgojIFNldHVwCgpgYGB7cn0KbGlicmFyeShmc3QpCmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeSh0aWR5cikKbGlicmFyeShwdXJycikKbGlicmFyeShmdXJycikKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkod2VzYW5kZXJzb24pCmxpYnJhcnkobG1lNCkKbGlicmFyeShsbWVyVGVzdCkKbGlicmFyeShtdWx0Y29tcCkKYGBgCgpgYGB7cn0Kc291cmNlKGZpbGUucGF0aCgiLi4iLCAic2NyaXB0cyIsICI5OV9zbGltc3RhbXBlbl9tb2RlbF9mdW5zLlIiKSkKYGBgCgpgYGB7cn0KZnV0dXJlOjpwbGFuKCJtdWx0aXNlc3Npb24iLCB3b3JrZXJzID0gNikgIyBTZXQgdG8gZGVzaXJlZCBudW1iZXIgb2YgY29yZXMKYGBgCgpgYGB7cn0KdGhlbWVfc2V0KHRoZW1lX2xpZ2h0KGJhc2Vfc2l6ZSA9IDE0KSArCiAgICAgICAgICAgIHRoZW1lKHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3VyID0gImJsYWNrIikpKQoKY29uZGl0aW9uX2NvbG91cnMgPC0gd2VzX3BhbGV0dGUoIkRhcmplZWxpbmcxIiwgbiA9IDUpCmNvbmRpdGlvbl9jb2xvdXJzW2MoMiwgNCwgNSldIDwtIGNvbmRpdGlvbl9jb2xvdXJzW2MoNCwgNSwgMildCgpkYXRhc2V0X2NvbG91cnMgPC0gd2VzX3BhbGV0dGUoIkRhcmplZWxpbmcyIiwgbiA9IDUpW2MoMiwgMyldCmBgYAoKCiMjIEhlbHBlciBmdW5jdGlvbnMKCmBgYHtyfQpsb2FkX3ByZWRpY3Rpb25zIDwtIGZ1bmN0aW9uKGNvdXJzZSkgewogIAogIHByZWRfdXNlciA8LSByZWFkX2ZzdChmaWxlLnBhdGgoIi4uIiwgImRhdGEiLCAicHJlZGljdGlvbnMiLCBwYXN0ZTAoInByZWRfdl9vYnNfdXNlcl8iLCBzdHJfcmVwbGFjZV9hbGwoY291cnNlLCAiICIsICJfIiksICIuZnN0IikpKQogIHByZWRfZmFjdCA8LSByZWFkX2ZzdChmaWxlLnBhdGgoIi4uIiwgImRhdGEiLCAicHJlZGljdGlvbnMiLCBwYXN0ZTAoInByZWRfdl9vYnNfZmFjdF8iLCBzdHJfcmVwbGFjZV9hbGwoY291cnNlLCAiICIsICJfIiksICIuZnN0IikpKQogIHByZWRfZmFjdF91c2VyIDwtIHJlYWRfZnN0KGZpbGUucGF0aCgiLi4iLCAiZGF0YSIsICJwcmVkaWN0aW9ucyIsIHBhc3RlMCgicHJlZF9mYWN0X2FuZF91c2VyXyIsIHN0cl9yZXBsYWNlX2FsbChjb3Vyc2UsICIgIiwgIl8iKSwgIi5mc3QiKSkpCiAgc2V0RFQocHJlZF91c2VyKQogIHNldERUKHByZWRfZmFjdCkKICBzZXREVChwcmVkX2ZhY3RfdXNlcikKICAKICBwcmVkX2RvbWFpbiA8LSBtZWFuKHVuaXF1ZShwcmVkX2ZhY3QsIGJ5ID0gYygiZmFjdF9pZCIpKSRwcmVkX2ZhY3QpCiAgcHJlZF9kZWZhdWx0IDwtIDAuMwogIAogICMgQ29tYmluZQogIHByZWRfYWxsIDwtIG1lcmdlKHByZWRfdXNlciwgcHJlZF9mYWN0LCBieSA9IGMoInVzZXJfaWQiLCAiZmFjdF9pZCIsICJhbHBoYSIsICJuX3JlcHMiKSwgYWxsID0gVFJVRSkKICBwcmVkX2FsbCA8LSBtZXJnZShwcmVkX2FsbCwgcHJlZF9mYWN0X3VzZXIsIGJ5ID0gYygidXNlcl9pZCIsICJmYWN0X2lkIiwgImFscGhhIiksIGFsbCA9IFRSVUUpCiAgcHJlZF9hbGxbLCBwcmVkX2RlZmF1bHQgOj0gcHJlZF9kZWZhdWx0XQogIHByZWRfYWxsWywgcHJlZF9kb21haW4gOj0gcHJlZF9kb21haW5dCiAgCiAgcHJlZF9vYnNfbG9uZyA8LSBwaXZvdF9sb25nZXIocHJlZF9hbGwsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHMgPSBwcmVkX3VzZXI6cHJlZF9kb21haW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAicHJlZGljdGlvbl90eXBlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lc19wcmVmaXggPSAicHJlZFxcXyIpCiAgCiAgc2V0RFQocHJlZF9vYnNfbG9uZykKICAKICAjIFJlbW92ZSBOQSBwcmVkaWN0aW9ucyBhbmQgcHJlZGljdGlvbnMgd2l0aG91dCBjb3JyZXNwb25kaW5nIG9ic2VydmF0aW9ucwogIHByZWRfb2JzX2xvbmcgPC0gcHJlZF9vYnNfbG9uZ1shaXMubmEodmFsdWUpXQogIHByZWRfb2JzX2xvbmcgPC0gcHJlZF9vYnNfbG9uZ1shaXMubmEoYWxwaGEpXQogIAogICMgUmVtb3ZlIGR1cGxpY2F0ZXMKICBwcmVkX29ic19sb25nIDwtIHVuaXF1ZShwcmVkX29ic19sb25nKQogIAogICMgU2V0IHByb3BlciBsYWJlbHMKICBjb25kaXRpb25fbGFiZWxzIDwtIGRhdGEudGFibGUocHJlZGljdGlvbl90eXBlID0gYygiZGVmYXVsdCIsICJkb21haW4iLCAiZmFjdCIsICJ1c2VyIiwgImZhY3RfdXNlciIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVkaWN0aW9uX2xhYmVsID0gZmFjdG9yKGMoIkRlZmF1bHQiLCAiRG9tYWluIiwgIkZhY3QiLCAiTGVhcm5lciIsICJGYWN0ICYgTGVhcm5lciIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIkRlZmF1bHQiLCAiRG9tYWluIiwgIkZhY3QiLCAiTGVhcm5lciIsICJGYWN0ICYgTGVhcm5lciIpKSkKICBwcmVkX29ic19sb25nIDwtIHByZWRfb2JzX2xvbmdbY29uZGl0aW9uX2xhYmVscywgb24gPSAuKHByZWRpY3Rpb25fdHlwZSldCiAgCiAgcmV0dXJuKHByZWRfb2JzX2xvbmcpCn0KYGBgCgoKCiMgUmF0ZSBvZiBmb3JnZXR0aW5nCgojIyBQcmVkaWN0ZWQgcmF0ZSBvZiBmb3JnZXR0aW5nCmBgYHtyfQpwcmVkX2dsIDwtIGxvYWRfcHJlZGljdGlvbnMoIkdyYW5kZXMgTGlnbmVzIikKcHJlZF9zcyA8LSBsb2FkX3ByZWRpY3Rpb25zKCJTdGVwcGluZyBTdG9uZXMiKQoKcHJlZF9ib3RoIDwtIHJiaW5kKHByZWRfZ2xbLCBjb3Vyc2UgOj0gIkZyZW5jaCJdLAogICAgICAgICAgICAgICAgICAgcHJlZF9zc1ssIGNvdXJzZSA6PSAiRW5nbGlzaCJdKQpgYGAKCiMjIyBEaXN0cmlidXRpb24gb2YgcHJlZGljdGlvbnMKYGBge3IgfQpwX3JvZl9kaXN0IDwtIGdncGxvdChwcmVkX2JvdGgsIGFlcyh4ID0gdmFsdWUsIGZpbGwgPSBwcmVkaWN0aW9uX2xhYmVsKSkgKwogIGZhY2V0X2dyaWQocHJlZGljdGlvbl9sYWJlbCB+IGNvdXJzZSwgc2NhbGVzID0gImZyZWVfeSIpICsKICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSwgYmlud2lkdGggPSAuMDEpICsKICBndWlkZXMoZmlsbCA9ICJub25lIikgKwogIGxhYnMoeCA9ICJQcmVkaWN0ZWQgcmF0ZS1vZi1mb3JnZXR0aW5nIiwKICAgICAgIHkgPSAiRGVuc2l0eSIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb25kaXRpb25fY29sb3VycykKCnBfcm9mX2Rpc3QKCmdnc2F2ZShwbG90ID0gcF9yb2ZfZGlzdCwgZmlsZS5wYXRoKCIuLiIsICJvdXRwdXQiLCAicm9mX3ByZWRpY3Rpb25zX2Rpc3RyaWJ1dGlvbi5wbmciKSwKICAgICAgIGRldmljZSA9ICJwbmciLCB3aWR0aCA9IDUsIGhlaWdodCA9IDcuNSkKYGBgCgojIyMgUHJlZGljdGVkIHZzLiBvYnNlcnZlZCB2YWx1ZXMKCldlIGNvbXBhcmUgdGhlIHByZWRpY3RlZCByYXRlIG9mIGZvcmdldHRpbmcgdG8gdGhlICJvYnNlcnZlZCIgcmF0ZSBvZiBmb3JnZXR0aW5nLCBpLmUuLCB0aGUgcmF0ZSBvZiBmb3JnZXR0aW5nIHRoYXQgd2FzIGVzdGltYXRlZCBhdCB0aGUgZW5kIG9mIHRoZSBsZWFybmluZyBzZXF1ZW5jZS4KClRvIGFzc2VzcyB0aGUgYWNjdXJhY3kgb2YgcHJlZGljdGlvbnMsIHdlIGNvbXB1dGUgdGhlIG1lYW4gYWJzb2x1dGUgZXJyb3IgKE1BRSkgYXMgYW4gYWdncmVnYXRlIHN0YXRpc3RpYywgYXMgd2VsbCBhcyB0aGUgYWJzb2x1dGUgZXJyb3IgKEFFKSBvZiBlYWNoIGluZGl2aWR1YWwgcHJlZGljdGlvbi4KCmBgYHtyfQpwcmVkX21hZSA8LSBwcmVkX2JvdGhbLCAuKG1hZSA9IG1lYW4oYWJzKGFscGhhIC0gdmFsdWUpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBhZV9zZSA9IHNkKGFicyhhbHBoYSAtIHZhbHVlKSkvLk4pLCAKICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gLihjb3Vyc2UsIHByZWRpY3Rpb25fbGFiZWwpXQoKICAKbl9vYnMgPC0gcHJlZF9ib3RoWywgLk4sIGJ5ID0gLihjb3Vyc2UsIHByZWRpY3Rpb25fbGFiZWwpXQpgYGAKClBsb3QgcHJlZGljdGVkIHZzLiBvYnNlcnZlZCB2YWx1ZXM6CmBgYHtyIH0Kcm9mX21pbiA8LSAwCnJvZl9tYXggPC0gMQpyb2ZfYnJlYWtzIDwtIHNlcSgwLjEsIDAuOSwgYnkgPSAuMikKCnBfcm9mX3ByZWRfdl9vYnMgPC0gZ2dwbG90KHByZWRfYm90aCwKICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gdmFsdWUsIHkgPSBhbHBoYSwgY29sb3VyID0gcHJlZGljdGlvbl9sYWJlbCkpICsKICAgIGZhY2V0X2dyaWQoY291cnNlIH4gcHJlZGljdGlvbl9sYWJlbCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC4zLCBsdHkgPSAyKSArCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLjMsIGx0eSA9IDIpICsKICAgIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCwgbHR5ID0gMywgYWxwaGEgPSAwLjc1KSArCiAgICBnZW9tX3BvaW50KGFscGhhID0gLjEsIHNpemUgPSAuMSwgcGNoID0gIi4iKSArCiAgICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBmb3JtdWxhID0geSB+IHgsIGNvbG91ciA9ICJibGFjayIpICsKICBnZW9tX2xhYmVsKGRhdGEgPSBwcmVkX21hZSwKICAgICAgICAgICAgIGFlcyhsYWJlbCA9IHBhc3RlKCJNQUUgPSIsIGZvcm1hdEMobWFlLCBkaWdpdHMgPSAzLCBmbGFnID0gIiMiKSkpLAogICAgICAgICAgICAgeCA9IHJvZl9tYXgsIHkgPSByb2ZfbWluLAogICAgICAgICAgICAgaGp1c3QgPSAxLCBjb2xvdXIgPSAiTkEiLCBzaXplID0gMywKICAgICAgICAgICAgIGFscGhhID0gLjksCiAgICAgICAgICAgICBsYWJlbC5zaXplID0gTkEpICsKICBnZW9tX3RleHQoZGF0YSA9IHByZWRfbWFlLAogICAgICAgICAgICBhZXMobGFiZWwgPSBwYXN0ZSgiTUFFID0iLCBmb3JtYXRDKG1hZSwgZGlnaXRzID0gMywgZmxhZyA9ICIjIikpKSwKICAgICAgICAgICAgeCA9IHJvZl9tYXgsIHkgPSByb2ZfbWluLAogICAgICAgICAgICBoanVzdCA9IDEsIGNvbG91ciA9ICJibGFjayIsIHNpemUgPSAzKSArCiAgZ2VvbV9sYWJlbChkYXRhID0gbl9vYnMsCiAgICAgICAgICAgICBhZXMobGFiZWwgPSBwYXN0ZSgibiA9Iiwgc2NhbGVzOjpjb21tYShOKSkpLAogICAgICAgICAgICAgeCA9IHJvZl9tYXgsCiAgICAgICAgICAgICB5ID0gcm9mX21heCwKICAgICAgICAgICAgIGhqdXN0ID0gMSwgY29sb3VyID0gIk5BIiwgc2l6ZSA9IDMsCiAgICAgICAgICAgICBhbHBoYSA9IC45LAogICAgICAgICAgICAgbGFiZWwuc2l6ZSA9IE5BKSArCiAgZ2VvbV90ZXh0KGRhdGEgPSBuX29icywKICAgICAgICAgICAgYWVzKGxhYmVsID0gcGFzdGUoIm4gPSIsIHNjYWxlczo6Y29tbWEoTikpKSwKICAgICAgICAgICAgeCA9IHJvZl9tYXgsCiAgICAgICAgICAgIHkgPSByb2ZfbWF4LAogICAgICAgICAgICBoanVzdCA9IDEsIGNvbG91ciA9ICJibGFjayIsIHNpemUgPSAzKSArCiAgZ3VpZGVzKGNvbG91ciA9ICJub25lIikgKwogIGxhYnMoeCA9ICJQcmVkaWN0ZWQgcmF0ZS1vZi1mb3JnZXR0aW5nIM6xIiwKICAgICAgIHkgPSAiT2JzZXJ2ZWQgcmF0ZS1vZi1mb3JnZXR0aW5nIM6xIikgKwogIGNvb3JkX2ZpeGVkKHJhdGlvID0gMSwgeGxpbSA9IGMocm9mX21pbiwgcm9mX21heCksIHlsaW0gPSBjKHJvZl9taW4sIHJvZl9tYXgpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHJvZl9icmVha3MpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gcm9mX2JyZWFrcykgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gY29uZGl0aW9uX2NvbG91cnMpCgpwX3JvZl9wcmVkX3Zfb2JzCgpnZ3NhdmUocGxvdCA9IHBfcm9mX3ByZWRfdl9vYnMsIGZpbGUucGF0aCgiLi4iLCAib3V0cHV0IiwgInJvZl9wcmVkaWN0ZWRfdnNfb2JzZXJ2ZWQucG5nIiksCiAgZGV2aWNlID0gInBuZyIsIHdpZHRoID0gMTAsIGhlaWdodCA9IDQuNSkKCnJtKHBfcm9mX3ByZWRfdl9vYnMpCmBgYAoKIyMjIFByZWRpY3Rpb24gZXJyb3IKCkNhbGN1bGF0ZSBwcmVkaWN0aW9uIGVycm9yOgpgYGB7cn0KcHJlZF9ib3RoWywgcHJlZGljdGlvbl9lcnJvciA6PSB2YWx1ZSAtIGFscGhhXQpgYGAKCkRpc3RyaWJ1dGlvbiBvZiBwcmVkaWN0aW9uIGVycm9yOgpgYGB7ciB9CnBfcm9mX3ByZWRfZXJyb3IgPC0gZ2dwbG90KHByZWRfYm90aCwgYWVzKHggPSBwcmVkaWN0aW9uX2Vycm9yLCBmaWxsID0gcHJlZGljdGlvbl9sYWJlbCkpICsKICBmYWNldF9ncmlkKHByZWRpY3Rpb25fbGFiZWwgfiBjb3Vyc2UsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksIGJpbndpZHRoID0gLjAxKSArCiAgZ3VpZGVzKGZpbGwgPSAibm9uZSIpICsKICBsYWJzKHggPSAiUmF0ZS1vZi1mb3JnZXR0aW5nIHByZWRpY3Rpb24gZXJyb3IgKHByZWRpY3RlZCAtIG9ic2VydmVkKSIsCiAgICAgICB5ID0gIkRlbnNpdHkiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29uZGl0aW9uX2NvbG91cnMpCgpwX3JvZl9wcmVkX2Vycm9yCgpnZ3NhdmUocGxvdCA9IHBfcm9mX3ByZWRfZXJyb3IsIGZpbGUucGF0aCgiLi4iLCAib3V0cHV0IiwgInJvZl9wcmVkaWN0aW9uX2Vycm9yLnBuZyIpLAogICAgICAgZGV2aWNlID0gInBuZyIsIHdpZHRoID0gNSwgaGVpZ2h0ID0gNy41KQpgYGAKCgojIyMjIEFic29sdXRlIHByZWRpY3Rpb24gZXJyb3IKClRvIGNvbXBhcmUgdGhlIG1hZ25pdHVkZSBvZiBwcmVkaWN0aW9uIGVycm9ycyBiZXR3ZWVuIHByZWRpY3Rpb24gbWV0aG9kcywgd2UgbG9vayBhdCBhYnNvbHV0ZSBwcmVkaWN0aW9uIGVycm9yLgoKYGBge3J9CnByZWRfYm90aFssIGFic19wcmVkaWN0aW9uX2Vycm9yIDo9IGFicyhwcmVkaWN0aW9uX2Vycm9yKV0KYGBgCgpEaXN0cmlidXRpb24gb2YgYWJzb2x1dGUgcHJlZGljdGlvbiBlcnJvcjoKYGBge3IgfQpwX3JvZl9hYnNfcHJlZF9lcnJvciA8LSBnZ3Bsb3QocHJlZF9ib3RoLCBhZXMoeCA9IGFic19wcmVkaWN0aW9uX2Vycm9yLCBmaWxsID0gcHJlZGljdGlvbl9sYWJlbCkpICsKICBmYWNldF9ncmlkKHByZWRpY3Rpb25fbGFiZWwgfiBjb3Vyc2UsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksIGJpbndpZHRoID0gLjAxKSArCiAgZ3VpZGVzKGZpbGwgPSAibm9uZSIpICsKICBsYWJzKHggPSAiQWJzb2x1dGUgcmF0ZS1vZi1mb3JnZXR0aW5nIHByZWRpY3Rpb24gZXJyb3IiLAogICAgICAgeSA9ICJEZW5zaXR5IikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbmRpdGlvbl9jb2xvdXJzKQoKcF9yb2ZfYWJzX3ByZWRfZXJyb3IKCmdnc2F2ZShwbG90ID0gcF9yb2ZfYWJzX3ByZWRfZXJyb3IsIGZpbGUucGF0aCgiLi4iLCAib3V0cHV0IiwgInJvZl9hYnNvbHV0ZV9wcmVkaWN0aW9uX2Vycm9yLnBuZyIpLAogICAgICAgZGV2aWNlID0gInBuZyIsIHdpZHRoID0gNSwgaGVpZ2h0ID0gNy41KQpgYGAKCmBgYHtyfQpwcmVkX2Vycm9yX3N1bW1hcmlzZWQgPC0gcHJlZF9ib3RoWywgLihlcnJvcl9tZWFuID0gbWVhbihhYnNfcHJlZGljdGlvbl9lcnJvciksIGVycm9yX3NlID0gc2QoYWJzX3ByZWRpY3Rpb25fZXJyb3IpLy5OKSwgYnkgPSAuKGNvdXJzZSwgcHJlZGljdGlvbl9sYWJlbCldCgpnZ3Bsb3QocHJlZF9lcnJvcl9zdW1tYXJpc2VkLCBhZXMoeCA9IHByZWRpY3Rpb25fbGFiZWwsIHkgPSBlcnJvcl9tZWFuLCBjb2xvdXIgPSBjb3Vyc2UpKSArCiAgZ2VvbV9ib3hwbG90KGRhdGEgPSBwcmVkX2JvdGgsCiAgICAgICAgICAgICAgIGFlcyh5ID0gYWJzX3ByZWRpY3Rpb25fZXJyb3IsIGdyb3VwID0gaW50ZXJhY3Rpb24oY291cnNlLCBwcmVkaWN0aW9uX2xhYmVsKSksCiAgICAgICAgICAgICAgIGNvbG91ciA9ICJncmV5NzAiLAogICAgICAgICAgICAgICB3aWR0aCA9IC4yNSwKICAgICAgICAgICAgICAgb3V0bGllci5zaGFwZSA9IE5BLAogICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gLjUpKSArCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IGVycm9yX21lYW4gLSBlcnJvcl9zZSwgeW1heCA9IGVycm9yX21lYW4gKyBlcnJvcl9zZSksIHdpZHRoID0gMCwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IC41KSkgKwogIGdlb21fcG9pbnQocG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IC41KSkgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCAwLjE3NSkpICsKICBsYWJzKHggPSAiTWV0aG9kIiwKICAgICAgIHkgPSAiQWJzb2x1dGUgcmF0ZS1vZi1mb3JnZXR0aW5nIHByZWRpY3Rpb24gZXJyb3IiLAogICAgICAgY29sb3VyID0gIkNvdXJzZSIpCmBgYAoKCgoKRml0IGEgcmVncmVzc2lvbiBtb2RlbCBvbiBhYnNvbHV0ZSBwcmVkaWN0aW9uIGVycm9yLgpUaGUgd2hvbGUgZGF0YSBzZXQgaXMgdG9vIGJpZyB0byBmaXQgaW4gYSByZWFzb25hYmxlIHRpbWUsIHNvIHdlIGZpdCB0aGUgbW9kZWwgdG8gYSByYW5kb20gc3Vic2V0IG9mIDFNIHByZWRpY3Rpb25zICh3aGljaCBhbHJlYWR5IHRha2VzIH4yNGhycykuIAoKIyMjIyMgR3JhbmRlcyBMaWduZXMKYGBge3J9Cm1fcm9mX3ByZWRfZXJyb3JfZ2xfZmlsZSA8LSBmaWxlLnBhdGgoIi4uIiwgImRhdGEiLCAibW9kZWxfZml0cyIsICJtX3ByZWRfZXJyb3JfR3JhbmRlc19MaWduZXNfMWU2LnJkYSIpCgppZiAoZmlsZS5leGlzdHMobV9yb2ZfcHJlZF9lcnJvcl9nbF9maWxlKSkgewogIGxvYWQobV9yb2ZfcHJlZF9lcnJvcl9nbF9maWxlKQp9IGVsc2UgewogIAogIHByZWRfZ2xfcmVnIDwtICgKICAgIHByZWRfYm90aAogICAgW2NvdXJzZSA9PSAiRnJlbmNoIl0KICAgIFtzYW1wbGUoLk4sIDFlNiksIC4ocHJlZGljdGlvbl9sYWJlbCwgYWJzX3ByZWRpY3Rpb25fZXJyb3IsIHVzZXJfaWQsIGZhY3RfaWQpXQogICkKICAKICBtX3JvZl9wcmVkX2Vycm9yX2dsIDwtIGxtZXIoYWJzX3ByZWRpY3Rpb25fZXJyb3IgfiBwcmVkaWN0aW9uX2xhYmVsICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKDEgfCB1c2VyX2lkKSArICgxIHwgZmFjdF9pZCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBwcmVkX2dsX3JlZykKICAKICBzYXZlKG1fcm9mX3ByZWRfZXJyb3JfZ2wsIGZpbGUgPSBtX3JvZl9wcmVkX2Vycm9yX2dsX2ZpbGUpCn0KCnN1bW1hcnkobV9yb2ZfcHJlZF9lcnJvcl9nbCkKYGBgCgpDb21wYXJlIGRpZmZlcmVudCBwcmVkaWN0aW9uIHR5cGVzIHRvIGVhY2ggb3RoZXI6CmBgYHtyfQpodF9nbCA8LSBnbGh0KG1fcm9mX3ByZWRfZXJyb3JfZ2wsIGxpbmZjdCA9IG1jcChwcmVkaWN0aW9uX2xhYmVsID0gIlR1a2V5IikpCnN1bW1hcnkoaHRfZ2wpCmBgYAoKSW5zcGVjdCB0aGUgbW9kZWwncyByZXNpZHVhbHM6CmBgYHtyfQpxcW5vcm0ocmVzaWQobV9yb2ZfcHJlZF9lcnJvcl9nbCkpCnFxbGluZShyZXNpZChtX3JvZl9wcmVkX2Vycm9yX2dsKSwgY29sID0gInJlZCIpCmBgYAoKYGBge3J9CnBsb3QobV9yb2ZfcHJlZF9lcnJvcl9nbCkKYGBgCgoKVGhlIFFRIHBsb3QgaW5kaWNhdGVzIHF1aXRlIGEgc3Ryb25nIHNrZXcsIHdoaWNoIGlzIG5vdCBzdXJwcmlzaW5nLCBnaXZlbiB0aGF0IHRoZSBkaXN0cmlidXRpb24gb2YgYWJzb2x1dGUgZXJyb3IgaXMgYm91bmRlZCBieSB6ZXJvIG9uIHRoZSBsZWZ0IGJ1dCB1bmJvdW5kZWQgb24gdGhlIHJpZ2h0LgpBc3N1bWluZyBhIEdhbW1hIGRpc3RyaWJ1dGlvbiBtYXkgYmUgYmV0dGVyLCBidXQgbW9kZWxzIHRoYXQgdXNlIGEgR2FtbWEgZGlzdHJpYnV0aW9uIGRvIG5vdCBjb252ZXJnZSBoZXJlLgpUaGUgTE1FUiBhbHNvIGdpdmVzIGEgc3VmZmljaWVudGx5IGFjY3VyYXRlIGVzdGltYXRlIG9mIHRoZSBtZWFucy4KCgoKIyMjIyMgU3RlcHBpbmcgU3RvbmVzCgpgYGB7cn0KbV9yb2ZfcHJlZF9lcnJvcl9zc19maWxlIDwtIGZpbGUucGF0aCgiLi4iLCAiZGF0YSIsICJtb2RlbF9maXRzIiwgIm1fcHJlZF9lcnJvcl9TdGVwcGluZ19TdG9uZXNfMWU2LnJkYSIpCgppZiAoZmlsZS5leGlzdHMobV9yb2ZfcHJlZF9lcnJvcl9zc19maWxlKSkgewogIGxvYWQobV9yb2ZfcHJlZF9lcnJvcl9zc19maWxlKQp9IGVsc2UgewogIAogIHByZWRfc3NfcmVnIDwtICgKICAgIHByZWRfYm90aAogICAgW2NvdXJzZSA9PSAiRW5nbGlzaCJdCiAgICBbc2FtcGxlKC5OLCAxZTYpLCAuKHByZWRpY3Rpb25fbGFiZWwsIGFic19wcmVkaWN0aW9uX2Vycm9yLCB1c2VyX2lkLCBmYWN0X2lkKV0KICApCiAgCiAgbV9wcmVkX2Vycm9yIDwtIGxtZXIoYWJzX3ByZWRpY3Rpb25fZXJyb3IgfiBwcmVkaWN0aW9uX2xhYmVsICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAoMSB8IHVzZXJfaWQpICsgKDEgfCBmYWN0X2lkKSwKICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gcHJlZF9zc19yZWcpCiAgCiAgc2F2ZShtX3ByZWRfZXJyb3IsIGZpbGUgPSBtX3JvZl9wcmVkX2Vycm9yX3NzX2ZpbGUpCn0KCnN1bW1hcnkobV9wcmVkX2Vycm9yKQpgYGAKCkNvbXBhcmUgZGlmZmVyZW50IHByZWRpY3Rpb24gdHlwZXMgdG8gZWFjaCBvdGhlcjoKYGBge3J9Cmh0X3NzIDwtIGdsaHQobV9wcmVkX2Vycm9yLCBsaW5mY3QgPSBtY3AocHJlZGljdGlvbl9sYWJlbCA9ICJUdWtleSIpKQpzdW1tYXJ5KGh0X3NzKQpgYGAKCkluc3BlY3QgdGhlIG1vZGVsJ3MgcmVzaWR1YWxzOgpgYGB7cn0KcXFub3JtKHJlc2lkKG1fcHJlZF9lcnJvcikpCnFxbGluZShyZXNpZChtX3ByZWRfZXJyb3IpLCBjb2wgPSAicmVkIikKYGBgCgoKIyMjIyMgQ29tcGFyaXNvbgpgYGB7cn0KaHRfZ2xfdGlkeSA8LSBicm9vbTo6dGlkeShjb25maW50KGh0X2dsKSkKaHRfc3NfdGlkeSA8LSBicm9vbTo6dGlkeShjb25maW50KGh0X3NzKSkKc2V0RFQoaHRfZ2xfdGlkeSkKc2V0RFQoaHRfc3NfdGlkeSkKCmh0X2JvdGhfdGlkeSA8LSByYmluZChodF9nbF90aWR5WywgY291cnNlIDo9ICJGcmVuY2giXSwKICAgICAgICAgICAgICAgICAgICAgIGh0X3NzX3RpZHlbLCBjb3Vyc2UgOj0gIkVuZ2xpc2giXSkKYGBgCgpgYGB7cn0KcF9yb2ZfcHJlZF9lcnJvcl9jb21wIDwtIGdncGxvdChodF9ib3RoX3RpZHksIGFlcyh4ID0gY29udHJhc3QsIHkgPSBlc3RpbWF0ZSwgeW1pbiA9IGNvbmYubG93LCB5bWF4ID0gY29uZi5oaWdoLCBjb2xvdXIgPSBjb3Vyc2UpKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAiMTEiLCBjb2xvdXIgPSAiZ3JleTYwIikgKwogIGdlb21fZXJyb3JiYXIod2lkdGggPSAwLjEpICsgCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHggPSAiTGluZWFyIGh5cG90aGVzZXMiLAogICAgICAgeSA9ICJFc3RpbWF0ZSIsCiAgICAgICBjYXB0aW9uID0gIlR1a2V5J3MgcmFuZ2UgdGVzdC4gRXJyb3IgYmFycyBzaG93IDk1JSBmYW1pbHktd2lzZSBjb25maWRlbmNlIGxldmVsLiIsCiAgICAgICBjb2xvdXIgPSAiQ291cnNlIikgKwogICAgY29vcmRfZmxpcCgpCgpwX3JvZl9wcmVkX2Vycm9yX2NvbXAKCmdnc2F2ZShwbG90ID0gcF9yb2ZfcHJlZF9lcnJvcl9jb21wLCBmaWxlLnBhdGgoIi4uIiwgIm91dHB1dCIsICJyb2ZfcHJlZGljdGlvbl9lcnJvcl9jb21wYXJpc29ucy5wbmciKSwKICAgICAgIGRldmljZSA9ICJwbmciLCB3aWR0aCA9IDcuNSwgaGVpZ2h0ID0gNSkKCmBgYAoKIyMjIyMgU3VtbWFyeSBwbG90CgpgYGB7cn0KIyBTZXQgc2lnbmlmaWNhbmNlIGxldmVsIG9mIGNvbXBhcmlzb25zIG1hbnVhbGx5LCBiYXNlZCBvbiBtb2RlbCBvdXRwdXQKcHJlZF9lcnJvcl9zdW1tYXJpc2VkJGNvbXBhcmlzb24gPC0gYygiKioqIikKcHJlZF9lcnJvcl9zdW1tYXJpc2VkW2MoMywgOCksIGNvbXBhcmlzb24gOj0gTkFdCnByZWRfZXJyb3Jfc3VtbWFyaXNlZFtjKDUsIDEwKSwgY29tcGFyaXNvbiA6PSAibi5zLiJdCgojIEFkZCBmaXR0ZWQgdmFsdWVzCnByZWRfZXJyb3Jfc3VtbWFyaXNlZFtjb3Vyc2UgPT0gIkZyZW5jaCIsIGVycm9yX2ZpdHRlZCA6PSBwcmVkaWN0KG1fcm9mX3ByZWRfZXJyb3JfZ2wsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld2RhdGEgPSBwcmVkX2Vycm9yX3N1bW1hcmlzZWRbY291cnNlID09ICJGcmVuY2giXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmUuZm9ybSA9IE5BLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJyZXNwb25zZSIpXQpwcmVkX2Vycm9yX3N1bW1hcmlzZWRbY291cnNlID09ICJFbmdsaXNoIiwgZXJyb3JfZml0dGVkIDo9IHByZWRpY3QobV9wcmVkX2Vycm9yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gcHJlZF9lcnJvcl9zdW1tYXJpc2VkW2NvdXJzZSA9PSAiRW5nbGlzaCJdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZS5mb3JtID0gTkEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gInJlc3BvbnNlIildCgpwX3JvZl9hYnNfcHJlZF9lcnJvcl9zdW1tIDwtIGdncGxvdChwcmVkX2Vycm9yX3N1bW1hcmlzZWQsIGFlcyh4ID0gcmVvcmRlcihwcmVkaWN0aW9uX2xhYmVsLCAtZXJyb3JfbWVhbiksIHkgPSBlcnJvcl9tZWFuLCBjb2xvdXIgPSBjb3Vyc2UpKSArCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IGVycm9yX21lYW4gLSBlcnJvcl9zZSwgeW1heCA9IGVycm9yX21lYW4gKyBlcnJvcl9zZSksIHdpZHRoID0gMCkgKwogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBjb3Vyc2UpLCBsdHkgPSAyKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gY29tcGFyaXNvbiksIAogICAgICAgICAgICBjb2xvdXIgPSAiYmxhY2siLAogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX251ZGdlKHggPSAuNSwgeSA9IGMocmVwKDAsIDkpLCAuMDAxKSksCiAgICAgICAgICAgIGhqdXN0ID0gLjUpICsKICBsYWJzKHggPSAiTWV0aG9kIiwKICAgICAgIHkgPSAiQWJzb2x1dGUgcmF0ZS1vZi1mb3JnZXR0aW5nIHByZWRpY3Rpb24gZXJyb3IiLAogICAgICAgY29sb3VyID0gIkNvdXJzZSIpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGRhdGFzZXRfY29sb3VycykgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoLjg1LCAuODUpKQoKcF9yb2ZfYWJzX3ByZWRfZXJyb3Jfc3VtbQoKZ2dzYXZlKHBsb3QgPSBwX3JvZl9hYnNfcHJlZF9lcnJvcl9zdW1tLCBmaWxlLnBhdGgoIi4uIiwgIm91dHB1dCIsICJyb2ZfYWJzb2x1dGVfcHJlZGljdGlvbl9lcnJvcl9zdW1tYXJ5LnBuZyIpLAogICAgICAgZGV2aWNlID0gInBuZyIsIHdpZHRoID0gNy41LCBoZWlnaHQgPSA0LjUpCmBgYAoKCmBgYHtyfQpwcmVkX2Vycm9yX3N1bW1hcmlzZWRbLCBwcmVkaWN0aW9uX3JhbmsgOj0gZnJhbmsoLWVycm9yX21lYW4pLCBieSA9IC4oY291cnNlKV0KCmFubm90YXRpb25fZGZfc3MgPC0gZGF0YS50YWJsZSgKICBjb3Vyc2UgPSByZXAoIkVuZ2xpc2giLCAxMCksCiAgc3RhcnQgPSBjKDEsIDEsIDEsIDEsCiAgICAgICAgICAgIDIsIDIsIDIsCiAgICAgICAgICAgIDMsIDMsCiAgICAgICAgICAgIDQKICApLAogIGVuZCA9IGMoMiwgMywgNCwgNSwKICAgICAgICAgIDMsIDQsIDUsCiAgICAgICAgICA0LCA1LAogICAgICAgICAgNQogICksCiAgeSA9IHNlcShtYXgocHJlZF9lcnJvcl9zdW1tYXJpc2VkJGVycm9yX21lYW4pKjEuMDEgKyAuMDA2NzUsIG1heChwcmVkX2Vycm9yX3N1bW1hcmlzZWQkZXJyb3JfbWVhbikqMS4wMSwgYnkgPSAtLjAwMDc1KSwKICBsYWJlbCA9IGMoInAgPCAuMDAxIiwgInAgPCAuMDAxIiwgInAgPCAuMDAxIiwgInAgPCAuMDAxIiwKICAgICAgICAgICAgInAgPCAuMDAxIiwgInAgPCAuMDAxIiwgInAgPCAuMDAxIiwKICAgICAgICAgICAgInAgPCAuMDAxIiwgInAgPCAuMDAxIiwKICAgICAgICAgICAgIm4ucy4iKQopCgphbm5vdGF0aW9uX2RmX2dsIDwtIGRhdGEudGFibGUoCiAgY291cnNlID0gcmVwKCJGcmVuY2giLCAxMCksCiAgc3RhcnQgPSBjKDEsIDEsIDEsIDEsCiAgICAgICAgICAgIDIsIDIsIDIsCiAgICAgICAgICAgIDMsIDMsCiAgICAgICAgICAgIDQKICApLAogIGVuZCA9IGMoMiwgMywgNCwgNSwKICAgICAgICAgIDMsIDQsIDUsCiAgICAgICAgICA0LCA1LAogICAgICAgICAgNQogICksCiAgeSA9IHNlcShtYXgocHJlZF9lcnJvcl9zdW1tYXJpc2VkJGVycm9yX21lYW4pKjEuMDEgKyAuMDA2NzUsIG1heChwcmVkX2Vycm9yX3N1bW1hcmlzZWQkZXJyb3JfbWVhbikqMS4wMSwgYnkgPSAtLjAwMDc1KSwKICBsYWJlbCA9IGMoInAgPCAuMDAxIiwgInAgPCAuMDAxIiwgInAgPCAuMDAxIiwgInAgPCAuMDAxIiwKICAgICAgICAgICAgInAgPCAuMDAxIiwgInAgPCAuMDAxIiwgInAgPCAuMDAxIiwKICAgICAgICAgICAgInAgPCAuMDAxIiwgInAgPCAuMDAxIiwKICAgICAgICAgICAgIm4ucy4iKQopCgphbm5vdGF0aW9uX2RmX3JvZiA8LSByYmluZChhbm5vdGF0aW9uX2RmX3NzLCBhbm5vdGF0aW9uX2RmX2dsKQphbm5vdGF0aW9uX2RmX3JvZlssIGxhYmVsIDo9IGZhY3RvcihsYWJlbCwgbGV2ZWxzID0gYygicCA8IC4wMDEiLCAicCA8IC4wMSIsICJwIDwgLjA1IiwgIm4ucy4iKSldCgpwX3JvZl9wcmVkX2Vycm9yX3N1bW1hcnkgPC0gZ2dwbG90KHByZWRfZXJyb3Jfc3VtbWFyaXNlZCwgYWVzKHggPSBwcmVkaWN0aW9uX3JhbmssIHkgPSBlcnJvcl9tZWFuKSkgKwogIGZhY2V0X2dyaWQofiBjb3Vyc2UpICsKICBnZW9tX2xpbmUoZGF0YSA9IGFubm90YXRpb25fZGZfcm9mLAogICAgICAgICAgICBhZXMoeCA9IDEsIHkgPSAuMDUsIGx0eSA9IGxhYmVsLCBhbHBoYSA9IGxhYmVsLCBjb2xvdXIgPSBOVUxMKSkgKyAjIER1bW15IGxpbmUgdG8gZ2V0IGxlZ2VuZAogIGdlb21fbGluZShhZXMoY29sb3VyID0gY291cnNlLCBncm91cCA9IGNvdXJzZSkpICsKICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluID0gZXJyb3JfbWVhbiAtIGVycm9yX3NlLCB5bWF4ID0gZXJyb3JfbWVhbiArIGVycm9yX3NlKSwgd2lkdGggPSAwKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0gY291cnNlLCBncm91cCA9IGNvdXJzZSkpICsKICBnZW9tX2xhYmVsKGFlcyhsYWJlbCA9IHByZWRpY3Rpb25fbGFiZWwpLCAKICAgICAgICAgICAgIGNvbG91ciA9ICJibGFjayIsIAogICAgICAgICAgICAgYWxwaGEgPSAuOSwKICAgICAgICAgICAgIGxhYmVsLnNpemUgPSBOQSwgCiAgICAgICAgICAgICBudWRnZV95ID0gLS4wMDI1KSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiQWJzb2x1dGUgcHJlZGljdGlvbiBlcnJvcjpcbnJhdGUtb2YtZm9yZ2V0dGluZyDOsSIsCiAgICAgICBjb2xvdXIgPSAiQ291cnNlIikgKwogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBleHBhbnNpb24oYWRkID0gLjc1KSwgYnJlYWtzID0gTlVMTCkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIE5BKSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gZGF0YXNldF9jb2xvdXJzKSArCiAgc2NhbGVfbGluZXR5cGVfbWFudWFsKHZhbHVlcyA9IGMoInAgPCAuMDAxIiA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInAgPCAuMDEiID0gNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAicCA8IC4wNSIgPSAyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJuLnMuIiA9IDMpLAogICAgICAgICAgICAgICAgICAgICAgICBkcm9wID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiUGFpcndpc2UgY29tcGFyaXNvbjoiKSArCiAgc2NhbGVfYWxwaGFfbWFudWFsKHZhbHVlcyA9IGMoInAgPCAuMDAxIiA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInAgPCAuMDEiID0gLjc1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJwIDwgLjA1IiA9IC41LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibi5zLiIgPSAuMjUpLAogICAgICAgICAgICAgICAgICAgICBkcm9wID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiUGFpcndpc2UgY29tcGFyaXNvbjoiKSArCiAgZ3VpZGVzKGNvbG91ciA9ICJub25lIikgKwogIGdnc2lnbmlmOjpnZW9tX3NpZ25pZihkYXRhID0gYW5ub3RhdGlvbl9kZl9yb2YsCiAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4bWluID0gc3RhcnQsIHhtYXggPSBlbmQsIGFubm90YXRpb25zID0gIiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgeV9wb3NpdGlvbiA9IHksIGx0eSA9IGxhYmVsLCBhbHBoYSA9IGxhYmVsKSwKICAgICAgICAgICAgICAgICAgICAgICAgdGlwX2xlbmd0aCA9IDAsCiAgICAgICAgICAgICAgICAgICAgICAgIG1hbnVhbCA9IFRSVUUpICArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb24gPSAicmlnaHQiKQoKcF9yb2ZfcHJlZF9lcnJvcl9zdW1tYXJ5CgpnZ3NhdmUoZmlsZS5wYXRoKCIuLiIsICJvdXRwdXQiLCAicm9mX2Fic29sdXRlX3ByZWRpY3Rpb25fZXJyb3Jfc3VtbWFyeS5wbmciKSwKICAgICAgIGRldmljZSA9ICJwbmciLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSA4KQpgYGAKCiMjIyMgSW1wcm92ZW1lbnQKCkhvdyBiaWcgd2FzIHRoZSBpbXByb3ZlbWVudCBmcm9tIHdvcnN0IHRvIGJlc3QgcHJlZGljdGlvbiBtZXRob2Q/CgpGcmVuY2g6CmBgYHtyfQojIEFic29sdXRlIGNoYW5nZQpodF9nbF90aWR5W2NvbnRyYXN0ID09ICJGYWN0IC0gRGVmYXVsdCIsIGVzdGltYXRlWzFdXQoKIyAlIGNoYW5nZQpzY2FsZXM6OnBlcmNlbnQoCiAgaHRfZ2xfdGlkeVtjb250cmFzdCA9PSAiRmFjdCAtIERlZmF1bHQiLCBlc3RpbWF0ZVsxXV0gLyBmaXhlZihtX3JvZl9wcmVkX2Vycm9yX2dsKVtbMV1dLAogIGFjY3VyYWN5ID0gLjEpCmBgYAoKRW5nbGlzaDoKYGBge3J9CiMgQWJzb2x1dGUgY2hhbmdlCmh0X3NzX3RpZHlbY29udHJhc3QgPT0gIkZhY3QgJiBMZWFybmVyIC0gRGVmYXVsdCIsIGVzdGltYXRlWzFdXQoKIyAlIGNoYW5nZQpzY2FsZXM6OnBlcmNlbnQoCiAgaHRfc3NfdGlkeVtjb250cmFzdCA9PSAiRmFjdCAmIExlYXJuZXIgLSBEZWZhdWx0IiwgZXN0aW1hdGVbMV1dIC8gZml4ZWYobV9wcmVkX2Vycm9yKVtbMV1dLAogIGFjY3VyYWN5ID0gLjEpCmBgYAoKCgojIyMgVmlzdWFsaXNlIHByZWRpY3Rpb24gZXJyb3IKCiMjIyMgQnkgbGVhcm5lcgoKYGBge3J9CnVzZXJfZnJlcSA8LSBwcmVkX2JvdGhbLCAuTiwgYnkgPSAuKGNvdXJzZSwgcHJlZGljdGlvbl9sYWJlbCwgdXNlcl9pZCldCgpwcmVkX3VzZXJfZnJlcSA8LSBwcmVkX2JvdGhbdXNlcl9mcmVxW04gPiA1MF0sIG9uID0gLihjb3Vyc2UsIHByZWRpY3Rpb25fbGFiZWwsIHVzZXJfaWQpXQoKcHJlZF91c2VyX3EgPC0gcHJlZF91c2VyX2ZyZXFbLCAuKHN0YXQgPSBjKCJ3aGlza2VyX2xvdyIsICJxMjUiLCAibWVkaWFuIiwgInE3NSIsICJ3aGlza2VyX2hpZ2giKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gYm94cGxvdC5zdGF0cyhwcmVkaWN0aW9uX2Vycm9yLCBkby5jb25mID0gRkFMU0UsIGRvLm91dCA9IEZBTFNFKSRzdGF0cyksIGJ5ID0gLihjb3Vyc2UsIHByZWRpY3Rpb25fbGFiZWwsIHVzZXJfaWQpXQoKcHJlZF91c2VyX3EgPC0gcGl2b3Rfd2lkZXIocHJlZF91c2VyX3EsIG5hbWVzX2Zyb20gPSAic3RhdCIsIHZhbHVlc19mcm9tID0gInZhbHVlIikKCnByZWRfdXNlcl9xIDwtIHByZWRfdXNlcl9xICU+JQogIGFycmFuZ2UoY291cnNlLCBwcmVkaWN0aW9uX2xhYmVsLCBtZWRpYW4pICU+JQogIGdyb3VwX2J5KGNvdXJzZSwgcHJlZGljdGlvbl9sYWJlbCkgJT4lCiAgbXV0YXRlKHVzZXJfb3JkZXIgPSAoMTpuKCkpL24oKSkKYGBgCgpgYGB7cn0KZ2dwbG90KHByZWRfdXNlcl9xLCBhZXMoeCA9IHVzZXJfb3JkZXIpKSArCiAgZmFjZXRfZ3JpZChjb3Vyc2UgfiBwcmVkaWN0aW9uX2xhYmVsKSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSB3aGlza2VyX2xvdywgeW1heCA9IHdoaXNrZXJfaGlnaCwgZmlsbCA9IGNvdXJzZSksIGFscGhhID0gLjMpICsKICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IHEyNSwgeW1heCA9IHE3NSwgZmlsbCA9IGNvdXJzZSksIGFscGhhID0gLjUpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBtZWRpYW4pLCBsd2QgPSAxKSArCiAgZ2VvbV9obGluZShkYXRhID0gTlVMTCwgeWludGVyY2VwdCA9IDAsIGx0eSA9IDMpICsKICAgIGxhYnMoeCA9ICJMZWFybmVycyIsCiAgICAgICB5ID0gIlJhdGUtb2YtZm9yZ2V0dGluZyBwcmVkaWN0aW9uIGVycm9yXG4ocHJlZGljdGVkIC0gb2JzZXJ2ZWQpIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGRhdGFzZXRfY29sb3VycykgKwogIGd1aWRlcyhmaWxsID0gIm5vbmUiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICBOVUxMCgpnZ3NhdmUoZmlsZS5wYXRoKCIuLiIsICJvdXRwdXQiLCAicm9mX3ByZWRpY3Rpb25fZXJyb3JfYnlfbGVhcm5lci5wbmciKSwKICBkZXZpY2UgPSAicG5nIiwgd2lkdGggPSAxMCwgaGVpZ2h0ID0gNC41KQpgYGAKCkFic29sdXRlIGVycm9yOgpgYGB7cn0KcHJlZF91c2VyX3EgPC0gcHJlZF91c2VyX2ZyZXFbLCAuKHN0YXQgPSBjKCJ3aGlza2VyX2xvdyIsICJxMjUiLCAibWVkaWFuIiwgInE3NSIsICJ3aGlza2VyX2hpZ2giKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gYm94cGxvdC5zdGF0cyhhYnNfcHJlZGljdGlvbl9lcnJvciwgZG8uY29uZiA9IEZBTFNFLCBkby5vdXQgPSBGQUxTRSkkc3RhdHMpLCBieSA9IC4oY291cnNlLCBwcmVkaWN0aW9uX2xhYmVsLCB1c2VyX2lkKV0KCnByZWRfdXNlcl9xIDwtIHBpdm90X3dpZGVyKHByZWRfdXNlcl9xLCBuYW1lc19mcm9tID0gInN0YXQiLCB2YWx1ZXNfZnJvbSA9ICJ2YWx1ZSIpCgpwcmVkX3VzZXJfcSA8LSBwcmVkX3VzZXJfcSAlPiUKICBhcnJhbmdlKGNvdXJzZSwgcHJlZGljdGlvbl9sYWJlbCwgbWVkaWFuKSAlPiUKICBncm91cF9ieShjb3Vyc2UsIHByZWRpY3Rpb25fbGFiZWwpICU+JQogIG11dGF0ZSh1c2VyX29yZGVyID0gKDE6bigpKS9uKCkpCgpnZ3Bsb3QocHJlZF91c2VyX3EsIGFlcyh4ID0gdXNlcl9vcmRlcikpICsKICBmYWNldF9ncmlkKGNvdXJzZSB+IHByZWRpY3Rpb25fbGFiZWwpICsKICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IHdoaXNrZXJfbG93LCB5bWF4ID0gd2hpc2tlcl9oaWdoLCBmaWxsID0gY291cnNlKSwgYWxwaGEgPSAuMykgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gcTI1LCB5bWF4ID0gcTc1LCBmaWxsID0gY291cnNlKSwgYWxwaGEgPSAuNSkgKwogIGdlb21fbGluZShhZXMoeSA9IG1lZGlhbiksIGx3ZCA9IDEpICsKICAgIGxhYnMoeCA9ICJMZWFybmVycyIsCiAgICAgICB5ID0gIkFic29sdXRlIHJhdGUtb2YtZm9yZ2V0dGluZyBwcmVkaWN0aW9uIGVycm9yIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGRhdGFzZXRfY29sb3VycykgKwogIGd1aWRlcyhmaWxsID0gIm5vbmUiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICBOVUxMCgpnZ3NhdmUoZmlsZS5wYXRoKCIuLiIsICJvdXRwdXQiLCAicm9mX2Fic19wcmVkaWN0aW9uX2Vycm9yX2J5X2xlYXJuZXIucG5nIiksCiAgZGV2aWNlID0gInBuZyIsIHdpZHRoID0gMTAsIGhlaWdodCA9IDQuNSkKYGBgCgpgYGB7cn0KZ2dwbG90KHByZWRfdXNlcl9xLCBhZXMoeCA9IHVzZXJfb3JkZXIsIGdyb3VwID0gcHJlZGljdGlvbl9sYWJlbCwgY29sb3VyID0gcHJlZGljdGlvbl9sYWJlbCkpICsKICBmYWNldF9ncmlkKH4gY291cnNlKSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBxMjUsIHltYXggPSBxNzUsIGZpbGwgPSBwcmVkaWN0aW9uX2xhYmVsKSwgY29sb3VyID0gTkEsIGFscGhhID0gLjE1KSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gbWVkaWFuKSwgbHdkID0gMSkgKwogICAgbGFicyh4ID0gIkxlYXJuZXJzIiwKICAgICAgIHkgPSAiQWJzb2x1dGUgcmF0ZS1vZi1mb3JnZXR0aW5nIHByZWRpY3Rpb24gZXJyb3IiLAogICAgICAgY29sb3VyID0gIlByZWRpY3Rpb25cbm1ldGhvZCIsCiAgICAgICBmaWxsID0gIlByZWRpY3Rpb25cbm1ldGhvZCIpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGNvbmRpdGlvbl9jb2xvdXJzKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29uZGl0aW9uX2NvbG91cnMpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIE5VTEwKCmdnc2F2ZShmaWxlLnBhdGgoIi4uIiwgIm91dHB1dCIsICJyb2ZfYWJzX3ByZWRpY3Rpb25fZXJyb3JfYnlfbGVhcm5lcl9jb25kZW5zZWQucG5nIiksCiAgZGV2aWNlID0gInBuZyIsIHdpZHRoID0gMTAsIGhlaWdodCA9IDQuNSkKYGBgCgoKIyMjIyBCeSBmYWN0CmBgYHtyfQpmYWN0X2ZyZXEgPC0gcHJlZF9ib3RoWywgLk4sIGJ5ID0gLihjb3Vyc2UsIHByZWRpY3Rpb25fbGFiZWwsIGZhY3RfaWQpXQoKcHJlZF9mYWN0X2ZyZXEgPC0gcHJlZF9ib3RoW2ZhY3RfZnJlcVtOID4gNTBdLCBvbiA9IC4oY291cnNlLCBwcmVkaWN0aW9uX2xhYmVsLCBmYWN0X2lkKV0KCnByZWRfZmFjdF9xIDwtIHByZWRfZmFjdF9mcmVxWywgLihzdGF0ID0gYygid2hpc2tlcl9sb3ciLCAicTI1IiwgIm1lZGlhbiIsICJxNzUiLCAid2hpc2tlcl9oaWdoIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IGJveHBsb3Quc3RhdHMocHJlZGljdGlvbl9lcnJvciwgZG8uY29uZiA9IEZBTFNFLCBkby5vdXQgPSBGQUxTRSkkc3RhdHMpLCBieSA9IC4oY291cnNlLCBwcmVkaWN0aW9uX2xhYmVsLCBmYWN0X2lkKV0KCnByZWRfZmFjdF9xIDwtIHBpdm90X3dpZGVyKHByZWRfZmFjdF9xLCBuYW1lc19mcm9tID0gInN0YXQiLCB2YWx1ZXNfZnJvbSA9ICJ2YWx1ZSIpCgpwcmVkX2ZhY3RfcSA8LSBwcmVkX2ZhY3RfcSAlPiUKICBhcnJhbmdlKGNvdXJzZSwgcHJlZGljdGlvbl9sYWJlbCwgbWVkaWFuKSAlPiUKICBncm91cF9ieShjb3Vyc2UsIHByZWRpY3Rpb25fbGFiZWwpICU+JQogIG11dGF0ZShmYWN0X29yZGVyID0gKDE6bigpKS9uKCkpCmBgYAoKYGBge3J9CmdncGxvdChwcmVkX2ZhY3RfcSwgYWVzKHggPSBmYWN0X29yZGVyKSkgKwogIGZhY2V0X2dyaWQoY291cnNlIH4gcHJlZGljdGlvbl9sYWJlbCkgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gd2hpc2tlcl9sb3csIHltYXggPSB3aGlza2VyX2hpZ2gsIGZpbGwgPSBjb3Vyc2UpLCBhbHBoYSA9IC4zKSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBxMjUsIHltYXggPSBxNzUsIGZpbGwgPSBjb3Vyc2UpLCBhbHBoYSA9IC41KSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gbWVkaWFuKSwgbHdkID0gMSkgKwogIGdlb21faGxpbmUoZGF0YSA9IE5VTEwsIHlpbnRlcmNlcHQgPSAwLCBsdHkgPSAzKSArCiAgICBsYWJzKHggPSAiRmFjdHMiLAogICAgICAgeSA9ICJSYXRlLW9mLWZvcmdldHRpbmcgcHJlZGljdGlvbiBlcnJvclxuKHByZWRpY3RlZCAtIG9ic2VydmVkKSIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBkYXRhc2V0X2NvbG91cnMpICsKICBndWlkZXMoZmlsbCA9ICJub25lIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgTlVMTAoKZ2dzYXZlKGZpbGUucGF0aCgiLi4iLCAib3V0cHV0IiwgInJvZl9wcmVkaWN0aW9uX2Vycm9yX2J5X2ZhY3QucG5nIiksCiAgZGV2aWNlID0gInBuZyIsIHdpZHRoID0gMTAsIGhlaWdodCA9IDQuNSkKYGBgCgpBYnNvbHV0ZSBlcnJvcjoKYGBge3J9CnByZWRfZmFjdF9xIDwtIHByZWRfZmFjdF9mcmVxWywgLihzdGF0ID0gYygid2hpc2tlcl9sb3ciLCAicTI1IiwgIm1lZGlhbiIsICJxNzUiLCAid2hpc2tlcl9oaWdoIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IGJveHBsb3Quc3RhdHMoYWJzX3ByZWRpY3Rpb25fZXJyb3IsIGRvLmNvbmYgPSBGQUxTRSwgZG8ub3V0ID0gRkFMU0UpJHN0YXRzKSwgYnkgPSAuKGNvdXJzZSwgcHJlZGljdGlvbl9sYWJlbCwgZmFjdF9pZCldCgpwcmVkX2ZhY3RfcSA8LSBwaXZvdF93aWRlcihwcmVkX2ZhY3RfcSwgbmFtZXNfZnJvbSA9ICJzdGF0IiwgdmFsdWVzX2Zyb20gPSAidmFsdWUiKQoKcHJlZF9mYWN0X3EgPC0gcHJlZF9mYWN0X3EgJT4lCiAgYXJyYW5nZShjb3Vyc2UsIHByZWRpY3Rpb25fbGFiZWwsIG1lZGlhbikgJT4lCiAgZ3JvdXBfYnkoY291cnNlLCBwcmVkaWN0aW9uX2xhYmVsKSAlPiUKICBtdXRhdGUoZmFjdF9vcmRlciA9ICgxOm4oKSkvbigpKQoKZ2dwbG90KHByZWRfZmFjdF9xLCBhZXMoeCA9IGZhY3Rfb3JkZXIpKSArCiAgZmFjZXRfZ3JpZChjb3Vyc2UgfiBwcmVkaWN0aW9uX2xhYmVsKSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSB3aGlza2VyX2xvdywgeW1heCA9IHdoaXNrZXJfaGlnaCwgZmlsbCA9IGNvdXJzZSksIGFscGhhID0gLjMpICsKICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IHEyNSwgeW1heCA9IHE3NSwgZmlsbCA9IGNvdXJzZSksIGFscGhhID0gLjUpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBtZWRpYW4pLCBsd2QgPSAxKSArCiAgICBsYWJzKHggPSAiRmFjdHMiLAogICAgICAgeSA9ICJBYnNvbHV0ZSByYXRlLW9mLWZvcmdldHRpbmcgcHJlZGljdGlvbiBlcnJvciIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBkYXRhc2V0X2NvbG91cnMpICsKICBndWlkZXMoZmlsbCA9ICJub25lIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgTlVMTAoKZ2dzYXZlKGZpbGUucGF0aCgiLi4iLCAib3V0cHV0IiwgInJvZl9hYnNfcHJlZGljdGlvbl9lcnJvcl9ieV9mYWN0LnBuZyIpLAogIGRldmljZSA9ICJwbmciLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSA0LjUpCmBgYAoKYGBge3J9CmdncGxvdChwcmVkX2ZhY3RfcSwgYWVzKHggPSBmYWN0X29yZGVyLCBncm91cCA9IHByZWRpY3Rpb25fbGFiZWwsIGNvbG91ciA9IHByZWRpY3Rpb25fbGFiZWwpKSArCiAgZmFjZXRfZ3JpZCh+IGNvdXJzZSkgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gcTI1LCB5bWF4ID0gcTc1LCBmaWxsID0gcHJlZGljdGlvbl9sYWJlbCksIGNvbG91ciA9IE5BLCBhbHBoYSA9IC4xNSkgKwogIGdlb21fbGluZShhZXMoeSA9IG1lZGlhbiksIGx3ZCA9IDEpICsKICAgIGxhYnMoeCA9ICJGYWN0cyIsCiAgICAgICB5ID0gIkFic29sdXRlIHJhdGUtb2YtZm9yZ2V0dGluZyBwcmVkaWN0aW9uIGVycm9yIiwKICAgICAgIGNvbG91ciA9ICJQcmVkaWN0aW9uXG5tZXRob2QiLAogICAgICAgZmlsbCA9ICJQcmVkaWN0aW9uXG5tZXRob2QiKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjb25kaXRpb25fY29sb3VycykgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbmRpdGlvbl9jb2xvdXJzKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICBOVUxMCgpnZ3NhdmUoZmlsZS5wYXRoKCIuLiIsICJvdXRwdXQiLCAicm9mX2Fic19wcmVkaWN0aW9uX2Vycm9yX2J5X2ZhY3RfY29uZGVuc2VkLnBuZyIpLAogIGRldmljZSA9ICJwbmciLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSA0LjUpCmBgYAoKCmBgYHtyfQpnZ3Bsb3QocHJlZF9ib3RoLCBhZXMoeCA9IGFic19wcmVkaWN0aW9uX2Vycm9yLCBjb2xvdXIgPSBwcmVkaWN0aW9uX2xhYmVsLCBmaWxsID0gcHJlZGljdGlvbl9sYWJlbCkpICsKICBmYWNldF9ncmlkKH4gY291cnNlKSArCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gLjEpICsKICAgICAgbGFicyh4ID0gIkFic29sdXRlIHJhdGUtb2YtZm9yZ2V0dGluZyBwcmVkaWN0aW9uIGVycm9yIiwKICAgICAgIHkgPSAiRGVuc2l0eSIsCiAgICAgICBjb2xvdXIgPSAiUHJlZGljdGlvblxubWV0aG9kIiwKICAgICAgIGZpbGwgPSAiUHJlZGljdGlvblxubWV0aG9kIikgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gY29uZGl0aW9uX2NvbG91cnMpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb25kaXRpb25fY29sb3VycykgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCAxMDApLCB4bGltID0gYygwLCAuMjUpKSArCiAgTlVMTAoKZ2dzYXZlKGZpbGUucGF0aCgiLi4iLCAib3V0cHV0IiwgInJvZl9hYnNfcHJlZGljdGlvbl9lcnJvcl9kZW5zaXR5LnBuZyIpLAogIGRldmljZSA9ICJwbmciLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSA0LjUpCmBgYAoKCgoKCiMgU2Vzc2lvbiBpbmZvCgpgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGAKCg==